﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Linq.Expressions;
using System.Reflection;
using System.Collections.ObjectModel;
using System.Text.RegularExpressions;
using Mysoft.Map.Extensions.DAL;
using System.Data;
using System.Data.SqlClient;
using System.Collections;

namespace Mysoft.QuickCode.DbExpressionExtensions
{
    /// <summary>
    /// 查询条件语句构建类
    /// </summary>
    /// <typeparam name="T"></typeparam>
    internal class EntityTableConditionBuilder<T> : ExpressionVisitor
        where T : class
    {
        /// <summary>
        /// SQL关键字映射SQL语句格式
        /// </summary>
        Dictionary<string, string> MethodConditions = new Dictionary<string, string>()
            {
                {"Contains","[{0}] LIKE '%' + @{1} + '%'"},
                {"Between","[{0}] Between @{1} AND @{2}"},
                {"In","[{0}] IN({1})"},
                {"NotIn","[{0}] NOT IN({1})"},
                {"InTable","[{0}] IN({1})"},
                {"NotInTable","[{0}] NOT IN({1})"},
                {"Equals","[{0}]=@{1}"},
                {"NotEquals","[{0}]<>@{1}"},
                {"IsNull","[{0}] IS NULL"},
                {"IsNotNull","[{0}] IS NOT NULL"},
                {"Le","[{0}]<=@{1}"},
                {"Ge","[{0}]>=@{1}"},
                {"Lt","[{0}]<@{1}"},
                {"Gt","[{0}]>@{1}"}
            };
        /// <summary>
        /// 需要从参数中解析MemberExpression的解析方式
        /// </summary>
        string[] MemberInArgs = new string[] { "In", "InTable", "Between", "NotEquals", "IsNull", "IsNotNull" };

        private List<object> m_arguments;
        private Stack<string> m_conditionParts;

        public string Condition { get; private set; }
        public object[] Arguments { get; set; }

        private EntityTable<T> _entityTable;

        /// <summary>
        /// 当前 VisitMethodCall 解释的方法表达式要生成的参数的个数，例如：Between需要生成2个参数才能完成格式化。
        /// </summary>
        private Stack<int> _callMethodParamterCount = new Stack<int>();

        /// <summary>
        /// entityTable对象的参数队列，用于暂存参数，最终会存入entityTable的参数栈中。
        /// </summary>
        private Queue<KeyValuePair<string, object>> _entityTableParamtersQueue = new Queue<KeyValuePair<string, object>>();

        private Stack<string> _khs = new Stack<string>();

        public EntityTableConditionBuilder(EntityTable<T> entityTable)
        {
            this._entityTable = entityTable;
        }

        /// <summary>
        /// 构建SQL查询的Where语句
        /// </summary>
        /// <param name="expression"></param>
        public void Build(Expression expression)
        {
            PartialEvaluator evaluator = new PartialEvaluator();
            Expression evaluatedExpression = evaluator.Eval(expression);

            this.m_arguments = new List<object>();
            this.m_conditionParts = new Stack<string>();

            this.Visit(evaluatedExpression);

            this.Arguments = this.m_arguments.ToArray();
            this.Condition = this.m_conditionParts.Count > 0 ? this.m_conditionParts.Pop() : null;
        }

        #region 内部方法

        /// <summary>
        /// 添加格式化参数到参数数组中
        /// </summary>
        /// <param name="paramList">参数数组</param>
        /// <param name="paramValues">参数值</param>
        protected void AddParameter(ref List<object> paramList, object[] paramValues)
        {
            for (int i = 0; i < paramValues.Length; i++)
            {
                string paramName = string.Format("p{0}", this._entityTable.Parameters.Count);    //生成的参数名称
                paramList.Add(paramName);
                //添加参数到entityTable对象
                this._entityTable.Parameters.Push(new KeyValuePair<string, object>(paramName, paramValues[i]));
            }
        }

        #endregion
        /// <summary>
        /// 判断左侧是否为CompareString，因为VB语法会转换为这个货
        /// </summary>
        /// <param name="b"></param>
        /// <param name="expressionType"></param>
        /// <returns></returns>
        protected BinaryExpression IsLeftCompareString(BinaryExpression b, ExpressionType expressionType)
        {
            var callExpression = b.Left as MethodCallExpression;
            if (callExpression == null)
            {
                return null;
            }
            if (callExpression.Method.Name == "CompareString")
            {
                switch (expressionType)
                {
                    case ExpressionType.Equal:
                        return BinaryExpression.Equal(callExpression.Arguments[0], callExpression.Arguments[1]);
                    case ExpressionType.NotEqual:
                        return BinaryExpression.NotEqual(callExpression.Arguments[0], callExpression.Arguments[1]);
                    case ExpressionType.GreaterThan:
                        return BinaryExpression.GreaterThan(callExpression.Arguments[0], callExpression.Arguments[1]);
                    case ExpressionType.GreaterThanOrEqual:
                        return BinaryExpression.GreaterThanOrEqual(callExpression.Arguments[0], callExpression.Arguments[1]);
                    case ExpressionType.LessThan:
                        return BinaryExpression.LessThan(callExpression.Arguments[0], callExpression.Arguments[1]);
                    case ExpressionType.LessThanOrEqual:
                        return BinaryExpression.LessThanOrEqual(callExpression.Arguments[0], callExpression.Arguments[1]);
                    default:
                        break;
                }
            }
            return null;
        }

        protected override Expression VisitBinary(BinaryExpression b)
        {
            if (b == null)
            {
                return b;
            }

            //检测是否CompareString方法
            var be = IsLeftCompareString(b, b.NodeType);
            if (be != null)
            {
                return VisitBinary(be);
            }

            string opr;
            switch (b.NodeType)
            {
                case ExpressionType.Equal:
                    opr = "=";
                    break;
                case ExpressionType.NotEqual:
                    opr = "<>";
                    break;
                case ExpressionType.GreaterThan:
                    opr = ">";
                    break;
                case ExpressionType.GreaterThanOrEqual:
                    opr = ">=";
                    break;
                case ExpressionType.LessThan:
                    opr = "<";
                    break;
                case ExpressionType.LessThanOrEqual:
                    opr = "<=";
                    break;
                case ExpressionType.AndAlso:
                    opr = "AND";
                    break;
                case ExpressionType.OrElse:
                    opr = "OR";
                    break;
                case ExpressionType.Add:
                    opr = "+";
                    break;
                case ExpressionType.Subtract:
                    opr = "-";
                    break;
                case ExpressionType.Multiply:
                    opr = "*";
                    break;
                case ExpressionType.Divide:
                    opr = "/";
                    break;
                default:
                    throw new NotSupportedException(b.NodeType + "不支持的NodeType");
            }

            //优先级括号入栈
            string sExp = b.ToString();
            _khs.Push(
                (sExp.StartsWith("(") && sExp.EndsWith(")"))
                ? "({0})"
                : "{0}");

            this.Visit(b.Left);
            this.Visit(b.Right);

            string right = this.m_conditionParts.Pop();
            string left = this.m_conditionParts.Pop();

            string condition = string.Format(_khs.Pop(), String.Format("{0} {1} {2}", left, opr, right));   //按优先级格式化
            this.m_conditionParts.Push(condition);

            return b;
        }

        /// <summary>
        /// 解析相关方法为SQL关键字
        /// </summary>
        /// <param name="m"></param>
        /// <returns></returns>
        protected override Expression VisitMethodCall(MethodCallExpression m)
        {
            if (m == null) { return m; }
            this.Visit(m.Object == null ? m.Arguments[0] : m.Object);
            if (m.Arguments.Count == 0)
            {
                return m;
            }

            //格式化的参数列表
            List<object> paramList = new List<object>();
            paramList.Add(this.m_conditionParts.Pop()); //字段名称

            //解析参数前，记录需要参数化的参数个数，用于后续 VisitMemberAccess 自动生成参数个数
            object[] expVals = ((m.Object == null) ? m.Arguments.Skip(1) : m.Arguments).ToArray();    //如果字段名称在数组中，则跳过第一个字段名称取参数

            //临时表名称
            string tempTableName = string.Empty;
            switch (m.Method.Name)
            {
                case "InTable":
                case "NotInTable":
                    {
                        //**********************************************************************
                        //Select In 比较特殊，因为经常会遇到In超长导致SQL报错问题，因此需要单独处理
                        //**********************************************************************

                        //解析出IN中的参数
                        object v = (expVals[0] as ConstantExpression).Value;
                        IList paramValues = (v.GetType().BaseType == typeof(object[]).BaseType) ? (IList)v : new object[] { v };

                        //创建DataTable，准备写入临时表
                        DataTable dt = new DataTable();
                        dt.Columns.Add("value");  //创建一个列名，用于存储字段
                        foreach (object val in paramValues)
                        {
                            DataRow row = dt.NewRow();
                            row[0] = val;
                            dt.Rows.Add(row);
                        }

                        //DataTable使用SqlBulkCopy写入临时表tempTableName 

                        using (ConnectionScope scope = new ConnectionScope())
                        {  //scope创建SqlBulkCopy，不需要启用事务。

                            tempTableName = string.Format("#{0}", Guid.NewGuid()).Replace("-", "_");    //表名不支持 “-” 线，因此替换成“_”。
                            CPQuery.From(string.Format("CREATE TABLE {0} ([value] VARCHAR(MAX))", tempTableName)).ExecuteNonQuery();

                            SqlBulkCopy bulkCopy = scope.CreateSqlBulkCopy(SqlBulkCopyOptions.Default | SqlBulkCopyOptions.FireTriggers);
                            bulkCopy.DestinationTableName = tempTableName;
                            bulkCopy.WriteToServer(dt); //写入到临时表
                            bulkCopy.Close();
                        }

                        break;
                    }
                default:
                    {
                        //非InTable，直接解析参数拼接为参数化
                        foreach (var item in expVals)
                        {
                            object v = (item as ConstantExpression).Value;
                            object[] paramValues = null;
                            if (v.GetType().BaseType == typeof(object[]).BaseType)
                            {
                                //数组类型的，转换为数组对象
                                IList arr = (IList)v;
                                List<object> lstObj = new List<object>();
                                foreach (object o in arr)
                                {
                                    lstObj.Add(o);
                                }
                                paramValues = lstObj.ToArray();
                            }
                            else
                            {
                                //装载为数据对象
                                paramValues = new object[] { v };
                            }
                            this.AddParameter(ref paramList, paramValues);
                        }
                        break;
                    }
            }

            //构建的条件语句
            string condition = string.Empty;
            string opr = string.Empty;
            switch (m.Method.Name)
            {
                case "In":
                case "NotIn":
                    {
                        //In语句拼接方式
                        string[] keys = paramList.Skip(1).Select(k => "@" + k.ToString()).ToArray();    //跳过第一个字段名称
                        //如果 IN 语句中没有查询的值，则直接当成 1=2 查询，因为 SELECT IN () 语法本身是错误的，就不该查到结果
                        //为了减少用户书写判断代码，因此支持这种处理方式
                        condition = (keys.Length == 0)
                            ? "1=2"
                            : String.Format(MethodConditions[m.Method.Name], paramList[0], string.Join(",", keys));
                        break;
                    }
                case "InTable":
                case "NotInTable":
                    {
                        //InTable语句拼接方式
                        condition = String.Format(MethodConditions[m.Method.Name], paramList[0],
                            ("select [value] from " + tempTableName));
                        break;
                    }
                case "ToString":
                    {
                        return m.Object;
                    }
                case "CompareString":
                    {
                        var binaryExpression = Expression.Equal(m.Arguments[0], m.Arguments[1]);
                        return VisitBinary(binaryExpression);
                    }
                case "Sqlable":
                    {
                        return Visit(m.Arguments[0]);
                    }
                default:
                    {
                        //其他通用拼接方式
                        if (MethodConditions.ContainsKey(m.Method.Name))
                        {
                            condition = String.Format(MethodConditions[m.Method.Name], paramList.ToArray());
                            break;
                        }
                        else
                        {
                            throw new NotSupportedException(m.Method.Name + " 不支持");
                        }
                    }
            }
            this.m_conditionParts.Push(condition);

            return m;
        }

        protected override Expression VisitConstant(ConstantExpression c)
        {
            string p = string.Format("p{0}", this._entityTable.Parameters.Count);
            this.m_conditionParts.Push(string.Format("@{0}", p));
            this._entityTable.Parameters.Push(new KeyValuePair<string, object>(p, c.Value));
            return c;
        }

        protected override Expression VisitMemberAccess(MemberExpression m)
        {
            //对于 Nullable`1 类型的对象处理
            Type mmType = m.Member.DeclaringType;
            Type nullableType = typeof(Nullable<>);

            string key = (mmType.Namespace == nullableType.Namespace && mmType.Name == nullableType.Name)
                ? _entityTable.PropertyToFileds[(m.Expression as MemberExpression).Member.Name]
                : _entityTable.PropertyToFileds[m.Member.Name];
            this.m_conditionParts.Push(key);
            return m;
        }
    }
}
